package rescuecore2.standard.kernel.comms;

import java.util.Collection;
import java.util.Map;

import kernel.AbstractCommunicationModel;
import rescuecore2.config.Config;
import rescuecore2.log.Logger;
import rescuecore2.messages.Command;
import rescuecore2.misc.collections.LazyMap;
import rescuecore2.standard.entities.AmbulanceCentre;
import rescuecore2.standard.entities.AmbulanceTeam;
import rescuecore2.standard.entities.BirdMan;
import rescuecore2.standard.entities.FireBrigade;
import rescuecore2.standard.entities.FireStation;
import rescuecore2.standard.entities.Human;
import rescuecore2.standard.entities.PoliceForce;
import rescuecore2.standard.entities.PoliceOffice;
import rescuecore2.standard.entities.StandardEntity;
import rescuecore2.standard.entities.StandardEntityURN;
import rescuecore2.standard.entities.StandardWorldModel;
import rescuecore2.standard.messages.AKSay;
import rescuecore2.standard.messages.AKTell;
import rescuecore2.worldmodel.Entity;
import rescuecore2.worldmodel.EntityID;
import rescuecore2.worldmodel.WorldModel;

/**
 * The legacy communication model: fire brigades talk to fire brigades and the
 * fire station, police to police, ambulance to ambulance and centres talk to
 * centres.
 */
public class StandardCommunicationModel extends AbstractCommunicationModel {
	private static final String SAY_RANGE_KEY = "comms.standard.say.range";
	private static final String PLATOON_MAX_KEY = "comms.standard.platoon.max";
	private static final String MAX_SIZE_KEY = "comms.standard.size.max";

	private StandardWorldModel world;
	private int sayDistance;
	private int maxSize;
	private int platoonMax;
	private int fsMax;
	private int poMax;
	private int acMax;
	private Map<EntityID, Integer> uttered;
	private Map<EntityID, Integer> heard;

	/**
	 * Construct a StandardCommunicationModel.
	 */
	public StandardCommunicationModel() {
		uttered = new LazyMap<EntityID, Integer>() {
			@Override
			public Integer createValue() {
				return 0;
			}
		};
		heard = new LazyMap<EntityID, Integer>() {
			@Override
			public Integer createValue() {
				return 0;
			}
		};
	}

	@Override
	public String toString() {
		return "Standard communication model";
	}

	@Override
	public void initialise(Config config, WorldModel<? extends Entity> model) {
		super.initialise(config, model);
		world = StandardWorldModel.createStandardWorldModel(model);
		sayDistance = config.getIntValue(SAY_RANGE_KEY);
		maxSize = config.getIntValue(MAX_SIZE_KEY);
		platoonMax = config.getIntValue(PLATOON_MAX_KEY);
		fsMax = world.getEntitiesOfType(StandardEntityURN.FIRE_BRIGADE).size() * 2;
		acMax = world.getEntitiesOfType(StandardEntityURN.AMBULANCE_TEAM)
				.size() * 2;
		poMax = world.getEntitiesOfType(StandardEntityURN.POLICE_FORCE).size() * 2;
	}

	@Override
	public void process(int time, Collection<? extends Command> agentCommands) {
		super.process(time, agentCommands);
		uttered.clear();
		heard.clear();
		for (Command next : agentCommands) {
			try {
				if (next instanceof AKSay) {
					processSay((AKSay) next);
				}
				if (next instanceof AKTell) {
					processTell((AKTell) next);
				}
			} catch (InvalidMessageException e) {
				Logger.warn("Invalid message: " + next, e);
			}
		}
	}

	private void processSay(AKSay say) throws InvalidMessageException {
		EntityID senderID = say.getAgentID();
		StandardEntity sender = world.getEntity(senderID);
		if (!(sender instanceof Human)) {
			throw new InvalidMessageException("Agent " + senderID
					+ " is not a human: "
					+ (sender == null ? "null" : sender.getClass().getName()));
		}
		byte[] data = say.getContent();
		if (data.length > maxSize) {
			throw new InvalidMessageException("Agent " + senderID
					+ " sent an oversize message: " + data.length + " > "
					+ maxSize);
		}
		int count = uttered.get(senderID);
		int max = getMessageMax(sender);
		if (count >= getMessageMax(sender)) {
			throw new InvalidMessageException("Agent " + senderID
					+ " has uttered too many messages: " + count + " >= " + max);
		}
		uttered.put(senderID, count + 1);
		for (StandardEntity receiver : world
				.getEntitiesOfType(StandardEntityURN.CIVILIAN,
						StandardEntityURN.FIRE_BRIGADE,
						StandardEntityURN.FIRE_STATION,
						StandardEntityURN.AMBULANCE_TEAM,
						StandardEntityURN.AMBULANCE_CENTRE,
						StandardEntityURN.POLICE_FORCE,
						StandardEntityURN.POLICE_OFFICE)) {
			int h = heard.get(receiver.getID());
			if (h >= getMessageMax(receiver)) {
				continue;
			}
			int distance = world.getDistance(sender, receiver);
			if (distance <= sayDistance) {
				addHearing(receiver, say);
				heard.put(receiver.getID(), h + 1);
			}
		}
	}

	private void processTell(AKTell tell) throws InvalidMessageException {
		EntityID senderID = tell.getAgentID();
		StandardEntity sender = world.getEntity(senderID);
		if (!(sender instanceof Human)) {
			throw new InvalidMessageException("Agent " + senderID
					+ " is not a human: "
					+ (sender == null ? "null" : sender.getClass().getName()));
		}
		byte[] data = tell.getContent();
		if (data.length > maxSize) {
			throw new InvalidMessageException("Agent " + senderID
					+ " sent an oversize message: " + data.length + " > "
					+ maxSize);
		}
		int count = uttered.get(senderID);
		int max = getMessageMax(sender);
		if (count >= getMessageMax(sender)) {
			throw new InvalidMessageException("Agent " + senderID
					+ " has uttered too many messages: " + count + " >= " + max);
		}
		uttered.put(senderID, count + 1);
		for (StandardEntity receiver : world
				.getEntitiesOfType(StandardEntityURN.CIVILIAN,
						StandardEntityURN.FIRE_BRIGADE,
						StandardEntityURN.FIRE_STATION,
						StandardEntityURN.AMBULANCE_TEAM,
						StandardEntityURN.AMBULANCE_CENTRE,
						StandardEntityURN.POLICE_FORCE,
						StandardEntityURN.POLICE_OFFICE)) {
			int h = heard.get(receiver.getID());
			if (h >= getMessageMax(receiver)) {
				continue;
			}
			if (canHear(receiver, sender)) {
				addHearing(receiver, tell);
				heard.put(receiver.getID(), h + 1);
			}
		}
	}

	private int getMessageMax(StandardEntity e) {
		if (e instanceof FireStation) {
			return fsMax;
		}
		if (e instanceof PoliceOffice) {
			return poMax;
		}
		if (e instanceof AmbulanceCentre) {
			return acMax;
		}
		if (e instanceof Human) {
			return platoonMax;
		}
		return 0;
	}

	private boolean canHear(StandardEntity receiver, StandardEntity sender) {
		if (receiver instanceof FireBrigade) {
			return sender instanceof FireBrigade
					|| sender instanceof FireStation;
		}
		if (receiver instanceof BirdMan) {
			return sender instanceof BirdMan;
		}
		if (receiver instanceof FireStation) {
			return sender instanceof FireBrigade
					|| sender instanceof FireStation
					|| sender instanceof PoliceOffice
					|| sender instanceof AmbulanceCentre;
		}
		if (receiver instanceof PoliceForce) {
			return sender instanceof PoliceForce
					|| sender instanceof PoliceOffice;
		}
		if (receiver instanceof PoliceOffice) {
			return sender instanceof PoliceForce
					|| sender instanceof FireStation
					|| sender instanceof PoliceOffice
					|| sender instanceof AmbulanceCentre;
		}
		if (receiver instanceof AmbulanceTeam) {
			return sender instanceof AmbulanceTeam
					|| sender instanceof AmbulanceCentre;
		}
		if (receiver instanceof AmbulanceCentre) {
			return sender instanceof AmbulanceTeam
					|| sender instanceof FireStation
					|| sender instanceof PoliceOffice
					|| sender instanceof AmbulanceCentre;
		}
		return false;
	}
}
